Skip to main content

Common mistakes with React Testing Library

Not using Testing Library ESLint plugins​

Importance: medium

Advice: Install and use the ESLint plugin for Testing Library.

Using wrapper as the variable name for the return value from render​

Importance: low

Advice: Destructure what you need from render or call it view.

// ❌
const wrapper = render(<Example prop="1" />);
wrapper.rerender(<Example prop="2" />);

// βœ…
const { rerender } = render(<Example prop="1" />);
rerender(<Example prop="2" />);

Using cleanup​

Importance: medium Advice: Don't use cleanup as it happens automatically now.

// ❌
import {render, screen, cleanup} from '@testing-library/react'

afterEach(cleanup)

// βœ…
import {render, screen} from '@testing-library/react'

Not using screen​

Importance: medium

Advice: Use screen for querying and debugging.

// ❌
const {getByRole} = render(<Example />)
const errorMessageNode = getByRole('alert')

// βœ…
render(<Example />)
const errorMessageNode = screen.getByRole('alert')

Using the wrong assertion​

Importance: high

Advice: Install and use @testing-library/jest-dom for better error messages.

const button = screen.getByRole('button', {name: /disabled button/i})

// ❌
expect(button.disabled).toBe(true)

// βœ…
expect(button).toBeDisabled()

Wrapping things in act unnecessarily​

Importance: medium

Advice: Learn when act is necessary and avoid unnecessary wrapping.

// ❌
act(() => {
render(<Example />)
})

// βœ…
render(<Example />)

Using the wrong query​

Importance: high

Advice: Use appropriate queries based on the DOM structure and accessibility.

// ❌
screen.getByTestId('username')

// βœ…
screen.getByRole('textbox', {name: /username/i})

Using query variants for anything except checking for non-existence*​

Importance: high

Advice: Use query* variants only for asserting element absence.

// ❌
expect(screen.queryByRole('alert')).toBeInTheDocument()

// βœ…
expect(screen.getByRole('alert')).toBeInTheDocument()

Using waitFor to wait for elements that can be queried with find*​

Importance: high

Advice: Use find* queries for elements that may not be available immediately.

// ❌
const submitButton = await waitFor(() =>
screen.getByRole('button', {name: /submit/i}),
)

// βœ…
const submitButton = await screen.findByRole('button', {name: /submit/i})

Passing an empty callback to waitFor​

Importance: high

Advice: Wait for a specific assertion inside waitFor.

// ❌
await waitFor(() => {})

// βœ…
await waitFor(() => expect(window.fetch).toHaveBeenCalledWith('foo'))

Having multiple assertions in a single waitFor callback

Importance: low

Advice: Only put one assertion in a callback.

// ❌
await waitFor(() => {
expect(window.fetch).toHaveBeenCalledWith('foo')
expect(window.fetch).toHaveBeenCalledTimes(1)
})

// βœ…
await waitFor(() => expect(window.fetch).toHaveBeenCalledWith('foo'))
expect(window.fetch).toHaveBeenCalledTimes(1)

Performing side-effects in waitFor​

Importance: high

Advice: Put side-effects outside waitFor callbacks.

// ❌
await waitFor(() => {
fireEvent.keyDown(input, {key: 'ArrowDown'})
expect(screen.getAllByRole('listitem')).toHaveLength(3)
})

// βœ…
fireEvent.keyDown(input, {key: 'ArrowDown'})
await waitFor(() => {
expect(screen.getAllByRole('listitem')).toHaveLength(3)
})

Using get variants as assertions*​

Importance: low

Advice: Make assertions explicit even with get* variants for clarity and debugging.

// ❌
screen.getByRole('alert', {name: /error/i})

// βœ…
expect(screen.getByRole('alert', {name: /error/i})).toBeInTheDocument()